﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Phoenix.Xna.Framework.Properties;

namespace Phoenix.Xna.Framework.Input
{
    /// <summary>
    /// The base class for all input devices.
    /// </summary>
    internal abstract class HumanInterfaceDevice : IHumanInterfaceDevice
    {
        /// <summary>
        /// Raised when the state of the interface device has changed.
        /// </summary>
        public event EventHandler StateChanged;

        /// <summary>
        /// Gets or sets the collection of valid IInputMap types.
        /// </summary>
        protected List<Type> ValidTypes { get; set; }

        /// <summary>
        /// Initializes a new instance of the HumanInterfaceDevice class.
        /// </summary>
        public HumanInterfaceDevice()
        {
            InputMaps = new List<IInputMap>();
            ValidTypes = new List<Type>();
        }

        /// <summary>
        /// Raises the StateChanged event.
        /// </summary>
        /// <param name="sender">The object the event originated from.</param>
        /// <param name="e">Event arguments sent with the event.</param>
        protected void OnStateChanged(object sender, EventArgs e)
        {
            EventHandler handler = StateChanged;

            if (handler != null)
            {
                handler(sender, e);
            }
        }

        private bool IsValidType(Type type)
        {
            return ValidTypes.Contains(type);
        }

        #region IHumanInterfaceDevice<S> Members

        /// <summary>
        /// Gets the type of the interface device.
        /// </summary>
        public DeviceType Type { get; protected set; }

        /// <summary>
        /// Gets the list of IInputMaps managed by the device.
        /// </summary>
        public List<IInputMap> InputMaps { get; protected set; }

        /// <summary>
        /// Updates the interface device.
        /// </summary>
        /// <param name="gameTime">A snapshot of the elapsed game time.</param>
        public virtual void Update(GameTime gameTime)
        {
            foreach (IInputMap map in InputMaps)
            {
                map.Update(gameTime);
            }
        }

        /// <summary>
        /// Registers a new IInputMap to be managed.
        /// </summary>
        /// <param name="map">The IInputMap to manage.</param>
        /// <exception cref="ArgumentException">Thrown when a duplicate IInputMap exists or if the supplied IInput map is not the correct type.</exception>
        public virtual void RegisterMap(IInputMap map)
        {
            if (IsValidType(map.GetType()))
            {
                if (Contains(map))
                {
                    throw new ArgumentException(Resources.InputMapExistsException);
                }

                InputMaps.Add(map);
            }
            else
            {
                throw new ArgumentException(String.Format(Resources.ArgumentExceptionInvalidType, "map", map.GetType().FullName));
            }
        }

        /// <summary>
        /// Unregisters the specified IInputMap from the manager.
        /// </summary>
        /// <param name="map">The IInputMap to unregister.</param>
        /// <exception cref="ArgumentException">Thrown when the supplied IInputMap is not a the correct type.</exception>
        public virtual void UnregisterMap(IInputMap map)
        {
            if (IsValidType(map.GetType()))
            {
                if (Contains(map))
                {
                    InputMaps.Remove(map);
                }
            }
            else
            {
                throw new ArgumentException(String.Format(Resources.ArgumentExceptionInvalidType, "map", map.GetType().FullName));
            }
        }

        /// <summary>
        /// Determins if the input device manages the specified IInputMap.
        /// </summary>
        /// <param name="map">The IInputMap to check for.</param>
        /// <returns>True if the map is managed, otherwise false.</returns>
        public bool Contains(IInputMap map)
        {
            return InputMaps.Contains(map);
        }

        /// <summary>
        /// Determins if the input device manages the specified IInputMap.
        /// </summary>
        /// <typeparam name="T">The type of the IInputMap to look for.</typeparam>
        /// <param name="map">The IInputMap to check for.</param>
        /// <returns>True if the map is managed, otherwise false.</returns>
        public bool Contains<T>(T map)
        {
            foreach (IInputMap m in InputMaps)
            {
                if (m.Equals(map))
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Determins if the input device manages the specified type of IInputMap.
        /// </summary>
        /// <typeparam name="T">The type of the IInputMap to check for.</typeparam>
        /// <returns>True if the type is managed, otherwise false.</returns>
        public bool ContainsMapType<T>()
        {
            foreach (IInputMap m in InputMaps)
            {
                if (m is T)
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Finds the first IInputMap that matches the specified type.
        /// </summary>
        /// <typeparam name="T">The type of IInputMap to look for.</typeparam>
        /// <returns>The first IInputMap of the specified type, otherwise null.</returns>
        public T FirstOf<T>()
        {
            foreach (IInputMap m in InputMaps)
            {
                if (m is T)
                {
                    return (T)m;
                }
            }

            return default(T);
        }

        #endregion
    }
}
